using System;
using System.Linq;
using System.Collections.Generic;

class EmitCtx
{
	int iarg, farg;

	public string Emit (char c) {
		switch (c) {
		case 'I':
			iarg += 1;
			return $"(int)(gssize)margs->iargs [{iarg - 1}]";
		case 'F':
			farg += 1;
			return $"*(float*)&margs->fargs [FIDX ({farg - 1})]";
		case 'L':
			iarg += 2;
			return $"get_long_arg (margs, {iarg - 2})";
		case 'D':
			farg += 1;
			return $"margs->fargs [FIDX ({farg - 1})]";
		default:
			throw new Exception ("IDK how to handle " + c);
		}
	}
}

class Driver {
	static string[] cookies = new string[] {
		"V",
		"VI",
		"VII",
		"VIII",
		"VIIII",
		"VIIIII",
		"VIIIIII",
		"VIIIIIII",
		"VIIIIIIII",
		"VIIIIIIIII",
		"VIIIIIIIIII",
		"VIIIIIIIIIII",
		"VIIIIIIIIIIII",
		"VIIIIIIIIIIIII",
		"VIIIIIIIIIIIIII",
		"I",
		"II",
		"III",
		"IIII",
		"IIIII",
		"IIIIII",
		"IIIIIII",
		"IIIIIIII",
		"IIIIIIIII",
		"IIIIIIIIII",
		"IIIIIIIIIII",
		"IIIIIIIIIIII",
		"IIIIIIIIIIIII",
		"IIIIIIIIIIIIII",
		"IILIIII",
		"IF",
		"ID",
		"IIF",
		"IIFI",
		"IIFF",
		"IFFII",
		"IIFII",		
		"IIFFI",
		"IIFFF",
		"IIFFFI",
		"IIFFII",
		"IIFIII",
		"IIFFFFI",
		"IIFFFFII",
		"IIIF",
		"IIIFI",
		"IIIFII",
		"IIIFIII",		
		"IIIIF",
		"IIIIFI",
		"IIIIFII",
		"IIIIFIII",
		"IIIFFFF",
		"IIIFFFFF",
		"IIFFFFFF",		
		"IIIFFFFFF",
		"IIIIIIIF",		
		"IIIIIIIFF",
		"IIFFFFFFFF",		
		"IIIFFFFFFFF",
		"IIIIIIFII",
		"IIIFFFFFFFFIII",
		"IIIIIFFFFIIII",
		"IFFFFFFI",
		"IIFFIII",
		"ILI",
		"IILLI",
		"L",
		"LL",
		"LI",
		"LIL",
		"LILI",
		"LILII",
		"DD",
		"DDI",
		"DDD",
		"DDDD",
		"VIF",
		"VIFF",
		"VIFFFF",
		"VIFFFFF",
		"VIFFFFFF",
		"VIFFFFFI",
		"VIIFFI",
		"FF",
		"FFI",
		"FFF",
		"FFFF",
		"DI",
		"FI",
		"IIL",
		"IILI",
		"IILIIIL",
		"IILLLI",
		"IDIII",
		"LII",
		"VID",
		"VILLI",
		"DID",
		"DIDD",
		"FIF",
		"FIFF",
		"LILL",
		"VIL",
	};
 
	static string TypeToSigType (char c) {
		switch (c) {
		case 'V': return "void";
		case 'I': return "int";
		case 'L': return "gint64";
		case 'F': return "float";
		case 'D': return "double";
		default:
			throw new Exception ("Can't handle " + c);
		}
	}

	static void Main (string[] args) {
		Console.WriteLine ("/*");
		Console.WriteLine ("* DON'T EDIT THIS FILE");
		Console.WriteLine ("* This file was generated by m2n-gen.cs - use it instead.");
		Console.WriteLine ("*/");
		foreach (var c in cookies) {
			Console.WriteLine ("static void");
			Console.WriteLine ($"wasm_invoke_{c.ToLower ()} (void *target_func, InterpMethodArguments *margs)");
			Console.WriteLine ("{");

			
			Console.Write ($"\ttypedef {TypeToSigType (c [0])} (*T)(");
			for (int i = 1; i < c.Length; ++i) {
				char p = c [i];
				if (i > 1)
					Console.Write (", ");
				Console.Write ($"{TypeToSigType (p)} arg_{i - 1}");
			}
			if (c.Length == 1)
				Console.Write ("void");

			Console.WriteLine (");\n\tT func = (T)target_func;");

			var ctx = new EmitCtx ();

			Console.Write ("\t");
			if (c [0] != 'V')
				Console.Write ($"{TypeToSigType (c [0])} res = ");

			Console.Write ("func (");
			for (int i = 1; i < c.Length; ++i) {
				char p = c [i];
				if (i > 1)
					Console.Write (", ");
				Console.Write (ctx.Emit (p));
			}
			Console.WriteLine (");");

			if (c [0] != 'V')
				Console.WriteLine ($"\t*({TypeToSigType (c [0])}*)margs->retval = res;");

			Console.WriteLine ("\n}\n");
		}

		Console.WriteLine ("static void\nicall_trampoline_dispatch (const char *cookie, void *target_func, InterpMethodArguments *margs)");
		Console.WriteLine ("{");

		if (args.Any(a => a == "--flat")) {
			for (int i = 0; i < cookies.Length; ++i) {
				var c = cookies [i];
				Console.Write ("\t");
				if (i > 0)
					Console.Write ("else ");
				Console.WriteLine ($"if (!strcmp (\"{c}\", cookie))");
				Console.WriteLine ($"\t\twasm_invoke_{c.ToLower ()} (target_func, margs);");
			}
			Console.WriteLine ("\telse {");
			Console.WriteLine ("\t\tg_error (\"CANNOT HANDLE COOKIE %s\\n\", cookie);");
			Console.WriteLine ("\t}");
		} else {
			Array.Sort (cookies);
			WritePartition (cookies, 0);
			Console.WriteLine ("\tg_error (\"CANNOT HANDLE COOKIE %s\\n\", cookie);");
		}

		Console.WriteLine ("}");
	}
	
	static void WritePartition (IEnumerable<string> set, int pos = 0, int depth = 0) {
		var prefix = "\t";
		for (var c = 0; c < pos; c++)
			prefix += "\t";

		var checks = 0;
		
		var groups = set.OrderBy (s => -s.Length).Where (s => s.Length > pos).GroupBy (s => s.Skip(pos).First().ToString());
		foreach (var g in groups) {
			Console.WriteLine ($"{prefix}{Elif (checks)} (cookie[{pos}] == '{g.Key}') {{");
			WritePartition (g.ToList (), pos + 1, checks + depth);
			Console.WriteLine ($"{prefix}}}");
			checks++;
		}

		var hits = set.Where (s => s.Length == pos);
		if (hits.Any ()) {
			Console.WriteLine ($"{prefix}{Elif (checks++)} (cookie[{pos}] == '\\0') {{");

			var h = hits.First ();
			Console.WriteLine ($"{prefix}\t// found: {h} depth {pos + checks + depth}");
			Console.WriteLine ($"{prefix}\twasm_invoke_{h.ToLower ()} (target_func, margs);");
			Console.WriteLine ($"{prefix}\treturn;");
			Console.WriteLine ($"{prefix}}}");
		}

		string Elif (int c) {
			return c == 0 ? "if" : "else if";
		}
	}
}
